﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using DG.Common.EF;
using DG.Common.EF.Reflection;
using TCC.ControleFluxoDeCaixa.Model.Contas;
using TCC.ControleFluxoDeCaixa.Model.Fluxo;
using TCC.ControleFluxoDeCaixa.Model.Fluxo.Credito;
using TCC.ControleFluxoDeCaixa.Model.Fluxo.TranferenciaEntreContas;
using TCC.ControleFluxoDeCaixa.Model.Gerenciadores;
using TCC.ControleFluxoDeCaixa.Model.Usuarios;

namespace TCC.ControleFluxoDeCaixa.Model.Entities
{
    public class FluxoDeCaixaContextFiltrado : IDbContext
    {
        private FluxoDeCaixaContext _ctx;

        public int? UsuarioLogadoID
        {
            get
            {
                var session = AutenticadorDeSessao.GetInstance().CurrentSession;
                return session == null ? (int?) null : session.Usuario.UsuarioId;
            }
        }


        public FluxoDeCaixaContextFiltrado(FluxoDeCaixaContext ctx)
        {
            this._ctx = ctx;

            Bancos = ctx.Bancos;
            FeriadosDataFixa = ctx.FeriadosDataFixa;
            FeriadosDataVariavel = ctx.FeriadosDataVariavel;
            BandeiraCreditos = ctx.BandeiraCreditos;
            Pessoas = ctx.Pessoas;
            TiposContaBancaria = ctx.TiposContaBancaria;
            Categorias = new FilteredDbSet<Categoria>(Context, c => c.Nome != Categoria.CategoriaTransferenciaNome);

            ContaFinanceiras = new FilteredDbSet<ContaFinanceira>(Context, c => c.Usuario.UsuarioId == UsuarioLogadoID);
            ContasCredito = new FilteredDbSet<ContaCredito>(Context, c => c.Usuario.UsuarioId == UsuarioLogadoID);
            FaturasCredito = new FilteredDbSet<FaturaCredito>(Context, f=> f.ContaCredito.Usuario.UsuarioId == UsuarioLogadoID);
            ContaFinanceiraBancarias =new FilteredDbSet<ContaFinanceiraBancaria>(Context, c => c.Usuario.UsuarioId == UsuarioLogadoID);
            
            
            Lancamentos = new FilteredDbSet<Lancamento>(Context, c => c.Usuario.UsuarioId == UsuarioLogadoID);
            LancamentosRecorrentes =new FilteredDbSet<LancamentoRecorrente>(Context, c => c.Conta.Usuario.UsuarioId == UsuarioLogadoID);
            LancamentoRecorrenteVencimentos = new FilteredDbSet<LancamentoRecorrenteVencimento>(Context, c => c.LancamentoRecorrente.Conta.Usuario.UsuarioId == UsuarioLogadoID);
            Movimentos = new FilteredDbSet<Movimento>(Context, c => c.Conta != null && c.Conta.Usuario.UsuarioId == UsuarioLogadoID);
            Emprestimos =new FilteredDbSet<Emprestimo>(Context, c => c.Usuario.UsuarioId == UsuarioLogadoID);
            Transferencias = new FilteredDbSet<TransferenciaEntreContas>(Context, c => c.Usuario.UsuarioId == UsuarioLogadoID);

            _cacheDbSets = new Dictionary<Type, IDbSet>();

            _cacheDbSets[typeof (FeriadoDataFixa)] = new AuxDbSet<FeriadoDataFixa>(FeriadosDataFixa);
            _cacheDbSets[typeof(FeriadoDataVariavel)] = new AuxDbSet<FeriadoDataVariavel>(FeriadosDataVariavel);
            _cacheDbSets[typeof(BandeiraCredito)] = new AuxDbSet<BandeiraCredito>(BandeiraCreditos);
            _cacheDbSets[typeof(Pessoa)] = new AuxDbSet<Pessoa>(Pessoas);
            _cacheDbSets[typeof(Banco)] = new AuxDbSet<Banco>(Bancos);
            _cacheDbSets[typeof(ContaFinanceira)] = new AuxDbSet<ContaFinanceira>(ContaFinanceiras);
            _cacheDbSets[typeof(ContaCredito)] = new AuxDbSet<ContaCredito>(ContasCredito);
            _cacheDbSets[typeof(ContaFinanceiraBancaria)] = new AuxDbSet<ContaFinanceiraBancaria>(ContaFinanceiraBancarias);
            _cacheDbSets[typeof(TipoDeContaBancaria)] = new AuxDbSet<TipoDeContaBancaria>(TiposContaBancaria);
            _cacheDbSets[typeof(Categoria)] = new AuxDbSet<Categoria>(Categorias);
            _cacheDbSets[typeof(Lancamento)] = new AuxDbSet<Lancamento>(Lancamentos);
            _cacheDbSets[typeof(LancamentoRecorrente)] = new AuxDbSet<LancamentoRecorrente>(LancamentosRecorrentes);
            _cacheDbSets[typeof(LancamentoRecorrenteVencimento)] = new AuxDbSet<LancamentoRecorrenteVencimento>(LancamentoRecorrenteVencimentos);
            _cacheDbSets[typeof(Movimento)] = new AuxDbSet<Movimento>(Movimentos);
            _cacheDbSets[typeof(Usuario)] = new AuxDbSet<Usuario>(Usuarios);
            _cacheDbSets[typeof(Emprestimo)] = new AuxDbSet<Emprestimo>(Emprestimos);
            _cacheDbSets[typeof(TransferenciaEntreContas)] = new AuxDbSet<TransferenciaEntreContas>(Transferencias);
            _cacheDbSets[typeof(FaturaCredito)] = new AuxDbSet<FaturaCredito>(FaturasCredito);
            
        }

        private class AuxDbSet<T> : IDbSet where T : class
        {
            private IDbSet<T> _set;

            public AuxDbSet(IDbSet<T> set)
            {
                _set = set;
            }

            public void Load()
            {
                _set.Load();
            }

            public IQueryable Query()
            {
                return _set;
            }

            public IEnumerable<object> Project()
            {
                _set.Load();
                return _set.Local;
            }


            public object Find(object[] keys)
            {
                return _set.Find(keys);
            }

            public void Remove(object entity)
            {
                _set.Remove((T) entity);
            }

            public void Add(object entity)
            {
                _set.Add((T) entity);
            }

            public IEnumerator GetEnumerator()
            {
                return _set.GetEnumerator();
            }

            public Expression Expression
            {
                get
                {
                    return _set.Expression;
                }
            }

            public Type ElementType
            {
                get { return _set.ElementType; }
            }

            public IQueryProvider Provider
            {
                get
                {
                    return _set.Provider;
                }
            }
        }

        public int SaveChanges()
        {
           return  _ctx.SaveChanges();
        }

        public DbContext Context
        {
            get
            {
                return _ctx;
            }
        }

        private Dictionary<Type, IDbSet> _cacheDbSets;
        public IDbSet Set(Type t)
        {
            return _cacheDbSets[t];
        }

        public DbSet<T> Set<T>() where T : class
        {
            return Context.Set<T>();
        }

        public IDbSet<Banco> Bancos { get; set; }

        public IDbSet<FeriadoDataFixa> FeriadosDataFixa { get; set; }
        public IDbSet<FeriadoDataVariavel> FeriadosDataVariavel { get; set; }
        public IDbSet<BandeiraCredito> BandeiraCreditos { get; set; }
        public IDbSet<Pessoa> Pessoas { get; set; }
        public IDbSet<ContaFinanceira> ContaFinanceiras { get; set; }
        public IDbSet<ContaCredito> ContasCredito { get; set; }
        public IDbSet<FaturaCredito> FaturasCredito { get; set; }
   
        public IDbSet<ContaFinanceiraBancaria> ContaFinanceiraBancarias { get; set; }
        public IDbSet<TipoDeContaBancaria> TiposContaBancaria { get; set; }
        public IDbSet<Categoria> Categorias { get ; set; }
        public IDbSet<Lancamento> Lancamentos { get; set; }
        public IDbSet<LancamentoRecorrente> LancamentosRecorrentes { get; set; }
        public IDbSet<LancamentoRecorrenteVencimento> LancamentoRecorrenteVencimentos { get; set; }
        public IDbSet<Movimento> Movimentos { get; set; }

        public IDbSet<Usuario> Usuarios
        {
            get
            {
                if (UsuarioLogadoID.HasValue)
                    return new FilteredDbSet<Usuario>(Context, c => c.UsuarioId == UsuarioLogadoID);
                return _ctx.Usuarios;
            }
        }

        public IDbSet<Emprestimo> Emprestimos { get; set; }
        public IDbSet<TransferenciaEntreContas> Transferencias { get; set; }

        public void Dispose()
        {
            Context.Dispose();
        }

        public ContextConceptualInfos Metadata
        {
            get
            {
                return _ctx.Metadata;
            }
        }

        public Usuario GetUsuarioLogado()
        {
            return Usuarios.SingleOrDefault(u => u.UsuarioId == UsuarioLogadoID);
        }

        public Categoria GetCategoriaJurosCartao()
        {
            var set = Context.Set<Categoria>();
            var transf = set.Local.SingleOrDefault(c => c.Nome == Categoria.CategoriaJurosCartaoNome);
            if (transf != null) return transf;
            transf = set.SingleOrDefault(c => c.Nome == Categoria.CategoriaJurosCartaoNome);
            if (transf == null) set.Add(transf = new Categoria { Nome = Categoria.CategoriaJurosCartaoNome });
            return transf;
        }

        public Categoria GetCategoriaTransferencia()
        {
            var set = Context.Set<Categoria>();
            var transf = set.Local.SingleOrDefault(c => c.Nome == Categoria.CategoriaTransferenciaNome);
            if (transf != null) return transf;
            transf = set.SingleOrDefault(c => c.Nome == Categoria.CategoriaTransferenciaNome);
            if (transf == null) set.Add(transf = new Categoria {Nome = Categoria.CategoriaTransferenciaNome});
            return transf;
        }

        public Categoria GetCategoriaEmprestimo()
        {
            var set = Context.Set<Categoria>();
            var emp = set.Local.SingleOrDefault(c => c.Nome == Categoria.CategoriaEmprestimoNome);
            if (emp != null) return emp;
            emp = set.SingleOrDefault(c => c.Nome == Categoria.CategoriaEmprestimoNome);
            if (emp == null) set.Add(emp = new Categoria { Nome = Categoria.CategoriaEmprestimoNome });
            return emp;
        }
    }
    
    public class FluxoDeCaixaContext : DbContext
    {
        public event SaveChangesExecutedHandler SaveChangesExecuted;
        public delegate void SaveChangesExecutedHandler(FluxoDeCaixaContext sender, DateTime dateTime);

        
        public FluxoDeCaixaContext()
            : base("FluxoDeCaixaTccConnectionString")
        {
            var frame = new StackFrame(1);
            var method = frame.GetMethod();
            var type = method.DeclaringType;
            if (type != typeof(FluxoContextFactory))
                throw new InvalidOperationException("Não é permitido instanciar o " + GetType().Name + " fora do " + typeof(FluxoContextFactory).Name);
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
            modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

            modelBuilder.Entity<Movimento>()
                .HasRequired(c => c.Lancamento)
                .WithMany()
                .WillCascadeOnDelete(true);
        }

        public DbSet<Banco> Bancos { get; set; }
        public DbSet<ContaFinanceira> ContaFinanceiras { get; set; }
        public DbSet<ContaCredito> ContasCredito { get; set; }
        public DbSet<ContaFinanceiraBancaria> ContaFinanceiraBancarias { get; set; }
        public DbSet<TipoDeContaBancaria> TiposContaBancaria { get; set; }
        public DbSet<Categoria> Categorias { get; set; }
        public DbSet<Lancamento> Lancamentos { get; set; }
        public DbSet<LancamentoRecorrente> LancamentosRecorrentes { get; set; }
        public DbSet<LancamentoRecorrenteVencimento> LancamentoRecorrenteVencimentos { get; set; }
        public DbSet<Movimento> Movimentos { get; set; }
        public DbSet<Usuario> Usuarios { get; set; }
        public DbSet<Pessoa> Pessoas { get; set; }
        public DbSet<Emprestimo> Emprestimos { get; set; }
        public DbSet<FeriadoDataFixa> FeriadosDataFixa { get; set; }
        public DbSet<FeriadoDataVariavel> FeriadosDataVariavel { get; set; }
        public DbSet<BandeiraCredito> BandeiraCreditos { get; set; }
        public DbSet<FaturaCredito> FaturasCredito { get; set; }

        
        public DbSet<TransferenciaEntreContas> Transferencias { get; set; }

        public override int SaveChanges()
        {
            try
            {
                var save = base.SaveChanges();

                if (save > 0 && SaveChangesExecuted != null)
                {
                    SaveChangesExecuted(this, DateTime.Now);
                }   
                return save;
            }
            catch (Exception e)
            {
                throw e;
            }


            
        }

        public ContextConceptualInfos Metadata
        {
            get
            {
                return ContextConceptualInfosFactory.GetContextEdmxForContext(this);
            }
        }

    }
    public class FilteredDbSet<TEntity> :
        IDbSet<TEntity>,
        IOrderedQueryable<TEntity>,
        IListSource

        where TEntity : class
    {
        private readonly DbSet<TEntity> _set;
        private readonly IQueryable<TEntity> _filteredSet;
        private readonly Action<TEntity> _initializeEntity;

        public FilteredDbSet(DbContext context)
            : this(context.Set<TEntity>(), i => true, null)
        {
        }

        public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter)
            : this(context.Set<TEntity>(), filter, null)
        {
        }

        public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
            : this(context.Set<TEntity>(), filter, initializeEntity)
        {
        }

        private FilteredDbSet(DbSet<TEntity> set, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
        {
            _set = set;
            _filteredSet = set.Where(filter);
            MatchesFilter = filter.Compile();
            _initializeEntity = initializeEntity;
        }

        public Func<TEntity, bool> MatchesFilter { get; private set; }

        public void ThrowIfEntityDoesNotMatchFilter(TEntity entity)
        {
            if (!MatchesFilter(entity))
                throw new ArgumentOutOfRangeException();
        }

        public TEntity Add(TEntity entity)
        {
            DoInitializeEntity(entity);
            ThrowIfEntityDoesNotMatchFilter(entity);
            return _set.Add(entity);
        }

        public TEntity Attach(TEntity entity)
        {
            ThrowIfEntityDoesNotMatchFilter(entity);
            return _set.Attach(entity);
        }

        public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
        {
            var entity = _set.Create<TDerivedEntity>();
            DoInitializeEntity(entity);
            return entity;
        }

        public TEntity Create()
        {
            var entity = _set.Create();
            DoInitializeEntity(entity);
            return entity;
        }

        public TEntity Find(params object[] keyValues)
        {
            var entity = _set.Find(keyValues);
            if (entity == null)
                return null;

            // If the user queried an item outside the filter, then we throw an error.
            // If IDbSet had a Detach method we would use it...sadly, we have to be ok with the item being in the Set.
            ThrowIfEntityDoesNotMatchFilter(entity);
            return entity;
        }

        public TEntity Remove(TEntity entity)
        {
            ThrowIfEntityDoesNotMatchFilter(entity);
            return _set.Remove(entity);
        }

        /// <summary>
        /// Returns the items in the local cache
        /// </summary>
        /// <remarks>
        /// It is possible to add/remove entities via this property that do NOT match the filter.
        /// Use the <see cref="ThrowIfEntityDoesNotMatchFilter"/> method before adding/removing an item from this collection.
        /// </remarks>
        public ObservableCollection<TEntity> Local { get { return _set.Local; } }

        IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator() { return _filteredSet.GetEnumerator(); }

        IEnumerator IEnumerable.GetEnumerator() { return _filteredSet.GetEnumerator(); }

        Type IQueryable.ElementType { get { return typeof(TEntity); } }

        Expression IQueryable.Expression { get { return _filteredSet.Expression; } }

        IQueryProvider IQueryable.Provider { get { return _filteredSet.Provider; } }

        bool IListSource.ContainsListCollection { get { return false; } }

        IList IListSource.GetList() { throw new InvalidOperationException(); }

        void DoInitializeEntity(TEntity entity)
        {
            if (_initializeEntity != null)
                _initializeEntity(entity);
        }
    }
}
